using System;
using DevExpress.Xpo;
using PostSharp.Extensibility;
using PostSharp.Laos;

namespace Atlantic.XpoAutomaticProperties
{
   /// <summary>
   /// Custom attribute that, when applied on a type that implements the interface 
   /// <see cref="IPropertyChangedNotifier.OnChanged"/> will automatically call 
   /// <see cref="IPropertyChangedNotifier"/> when any property of the target 
   /// type is modified and will instantiate any automatic <see cref="XPCollection{T}"/> or
   /// <see cref="XPCollection"/> backing fields.
   /// </summary>
   [MulticastAttributeUsage(MulticastTargets.Class)]
   [Serializable]
   public sealed class XpoAutomaticPropertiesAttribute : CompoundAspect
   {
      #region Fields

      [NonSerialized] private int aspectPriority;

      #endregion

      #region Methods

      /// <summary>
      /// Method called at compile time to get individual aspects required by the current compound
      /// aspect.
      /// </summary>
      /// <param name="targetElement">Metadata element (<see cref="Type"/> in our case) to which
      /// the current custom attribute instance is applied.</param>
      /// <param name="collection">Collection of aspects to which individual aspects should be
      /// added.</param>
      public override void ProvideAspects(object targetElement, LaosReflectionAspectCollection collection)
      {
         // Get the target type.
         var targetType = (Type) targetElement;

         foreach (var property in targetType.UnderlyingSystemType.GetProperties())
         {
            if (!property.DeclaredIn(targetType)) continue;
            if (property.IsIgnored()) continue;

            // Attach change notification to non-collection property types.
            if (property.CanWrite && property.CanRead && !property.IsCollection())
            {
               // We have a writable (and readable) property, but we want to ignore collection types.
               // Worth noting that the check on property.DeclaringType means that we must mark each type with the attribute...no bad thing!

               // Remember that this happens at compile time, so write to the output window if you want some debug info.
               //AopUtils.Report("####", property.Name, "not collection!");

               var setMethod = property.GetSetMethod(true);
               if (!setMethod.IsStatic)
               {
                  var aspect = new PropertyChangedNotifierSubAspect(this, property.Name);
                  collection.AddAspect(setMethod, aspect);
               }
            }

            // Instantiate collection property backing fields.
            if (property.IsXPCollection())
            {
               var getMethod = property.GetGetMethod(true);
               if (!getMethod.IsStatic)
               {
                  var backingField = AopUtils.GetBackingField(targetType, property);
                  var aspect = new CollectionCreatorSubAspect(this, property.Name, property.PropertyType, backingField);
                  collection.AddAspect(backingField, aspect);
               }
            }
         }
      }

      #endregion

      #region Properties

      /// <summary>
      /// Gets or sets the priority of the property-level aspect.
      /// </summary>
      /// <remarks>
      /// Give a large number to have the event raisen after any other
      /// on-success aspect on the properties of this type. The default value
      /// is 0.
      /// </remarks>
      public int AspectPriority
      {
         get { return aspectPriority; }
         set { aspectPriority = value; }
      }

      #endregion
   }
}